﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace RH.EBS.Core.Repository.Tools
{
	public static class ExpressionExtensions
	{
		public static Expression Visit<T>(
			this Expression exp,
			Func<T, Expression> visitor ) where T : Expression
		{
			return ExpressionVisitor<T>.Visit( exp, visitor );
		}

		public static TExp Visit<T, TExp>(
			this TExp exp,
			Func<T, Expression> visitor )
			where T : Expression
			where TExp : Expression
		{
			return (TExp)ExpressionVisitor<T>.Visit( exp, visitor );
		}

		public static Expression<TDelegate> Visit<T, TDelegate>(
			this Expression<TDelegate> exp,
			Func<T, Expression> visitor ) where T : Expression
		{
			return ExpressionVisitor<T>.Visit<TDelegate>( exp, visitor );
		}

		public static IQueryable<TSource> Visit<T, TSource>(
			this IQueryable<TSource> source,
			Func<T, Expression> visitor ) where T : Expression
		{
			return source.Provider.CreateQuery<TSource>( ExpressionVisitor<T>.Visit( source.Expression, visitor ) );
		}
	}

	/// <summary>
	/// This class visits every Parameter expression in an expression tree and calls a delegate
	/// to optionally replace the parameter.  This is useful where two expression trees need to
	/// be merged (and they don't share the same ParameterExpressions).
	/// </summary>
	public class ExpressionVisitor<T> : ExpressionVisitor where T : Expression
	{
		Func<T, Expression> visitor;

		public ExpressionVisitor( Func<T, Expression> visitor )
		{
			this.visitor = visitor;
		}

		public static Expression Visit(
			Expression exp,
			Func<T, Expression> visitor )
		{
			return new ExpressionVisitor<T>( visitor ).Visit( exp );
		}

		public static Expression<TDelegate> Visit<TDelegate>(
			Expression<TDelegate> exp,
			Func<T, Expression> visitor )
		{
			return (Expression<TDelegate>)new ExpressionVisitor<T>( visitor ).Visit( exp );
		}

		protected override Expression Visit( Expression exp )
		{
			if ( exp is T && visitor != null ) exp = visitor( (T)exp );

			return base.Visit( exp );
		}
	}

	/// <summary>
	/// Expression visitor
	/// (from http://blogs.msdn.com/mattwar/archive/2007/07/31/linq-building-an-iqueryable-provider-part-ii.aspx)
	/// </summary>
	public abstract class ExpressionVisitor
	{
		protected ExpressionVisitor()
		{
		}

		protected virtual Expression Visit( Expression exp )
		{
			if ( exp == null )
				return exp;
			switch ( exp.NodeType )
			{
				case ExpressionType.Negate:
				case ExpressionType.NegateChecked:
				case ExpressionType.Not:
				case ExpressionType.Convert:
				case ExpressionType.ConvertChecked:
				case ExpressionType.ArrayLength:
				case ExpressionType.Quote:
				case ExpressionType.TypeAs:
					return this.VisitUnary( (UnaryExpression)exp );
				case ExpressionType.Add:
				case ExpressionType.AddChecked:
				case ExpressionType.Subtract:
				case ExpressionType.SubtractChecked:
				case ExpressionType.Multiply:
				case ExpressionType.MultiplyChecked:
				case ExpressionType.Divide:
				case ExpressionType.Modulo:
				case ExpressionType.And:
				case ExpressionType.AndAlso:
				case ExpressionType.Or:
				case ExpressionType.OrElse:
				case ExpressionType.LessThan:
				case ExpressionType.LessThanOrEqual:
				case ExpressionType.GreaterThan:
				case ExpressionType.GreaterThanOrEqual:
				case ExpressionType.Equal:
				case ExpressionType.NotEqual:
				case ExpressionType.Coalesce:
				case ExpressionType.ArrayIndex:
				case ExpressionType.RightShift:
				case ExpressionType.LeftShift:
				case ExpressionType.ExclusiveOr:
					return this.VisitBinary( (BinaryExpression)exp );
				case ExpressionType.TypeIs:
					return this.VisitTypeIs( (TypeBinaryExpression)exp );
				case ExpressionType.Conditional:
					return this.VisitConditional( (ConditionalExpression)exp );
				case ExpressionType.Constant:
					return this.VisitConstant( (ConstantExpression)exp );
				case ExpressionType.Parameter:
					return this.VisitParameter( (ParameterExpression)exp );
				case ExpressionType.MemberAccess:
					return this.VisitMemberAccess( (MemberExpression)exp );
				case ExpressionType.Call:
					return this.VisitMethodCall( (MethodCallExpression)exp );
				case ExpressionType.Lambda:
					return this.VisitLambda( (LambdaExpression)exp );
				case ExpressionType.New:
					return this.VisitNew( (NewExpression)exp );
				case ExpressionType.NewArrayInit:
				case ExpressionType.NewArrayBounds:
					return this.VisitNewArray( (NewArrayExpression)exp );
				case ExpressionType.Invoke:
					return this.VisitInvocation( (InvocationExpression)exp );
				case ExpressionType.MemberInit:
					return this.VisitMemberInit( (MemberInitExpression)exp );
				case ExpressionType.ListInit:
					return this.VisitListInit( (ListInitExpression)exp );
				default:
					throw new Exception( string.Format( "Unhandled expression type: '{0}'", exp.NodeType ) );
			}
		}

		protected virtual MemberBinding VisitBinding( MemberBinding binding )
		{
			switch ( binding.BindingType )
			{
				case MemberBindingType.Assignment:
					return this.VisitMemberAssignment( (MemberAssignment)binding );
				case MemberBindingType.MemberBinding:
					return this.VisitMemberMemberBinding( (MemberMemberBinding)binding );
				case MemberBindingType.ListBinding:
					return this.VisitMemberListBinding( (MemberListBinding)binding );
				default:
					throw new Exception( string.Format( "Unhandled binding type '{0}'", binding.BindingType ) );
			}
		}

		protected virtual ElementInit VisitElementInitializer( ElementInit initializer )
		{
			ReadOnlyCollection<Expression> arguments = this.VisitExpressionList( initializer.Arguments );
			if ( arguments != initializer.Arguments )
			{
				return Expression.ElementInit( initializer.AddMethod, arguments );
			}
			return initializer;
		}

		protected virtual Expression VisitUnary( UnaryExpression u )
		{
			Expression operand = this.Visit( u.Operand );
			if ( operand != u.Operand )
			{
				return Expression.MakeUnary( u.NodeType, operand, u.Type, u.Method );
			}
			return u;
		}

		protected virtual Expression VisitBinary( BinaryExpression b )
		{
			Expression left = this.Visit( b.Left );
			Expression right = this.Visit( b.Right );
			Expression conversion = this.Visit( b.Conversion );
			if ( left != b.Left || right != b.Right || conversion != b.Conversion )
			{
				if ( b.NodeType == ExpressionType.Coalesce && b.Conversion != null )
					return Expression.Coalesce( left, right, conversion as LambdaExpression );
				else
					return Expression.MakeBinary( b.NodeType, left, right, b.IsLiftedToNull, b.Method );
			}
			return b;
		}

		protected virtual Expression VisitTypeIs( TypeBinaryExpression b )
		{
			Expression expr = this.Visit( b.Expression );
			if ( expr != b.Expression )
			{
				return Expression.TypeIs( expr, b.TypeOperand );
			}
			return b;
		}

		protected virtual Expression VisitConstant( ConstantExpression c )
		{
			return c;
		}

		protected virtual Expression VisitConditional( ConditionalExpression c )
		{
			Expression test = this.Visit( c.Test );
			Expression ifTrue = this.Visit( c.IfTrue );
			Expression ifFalse = this.Visit( c.IfFalse );
			if ( test != c.Test || ifTrue != c.IfTrue || ifFalse != c.IfFalse )
			{
				return Expression.Condition( test, ifTrue, ifFalse );
			}
			return c;
		}

		protected virtual Expression VisitParameter( ParameterExpression p )
		{
			return p;
		}

		protected virtual Expression VisitMemberAccess( MemberExpression m )
		{
			Expression exp = this.Visit( m.Expression );
			if ( exp != m.Expression )
			{
				return Expression.MakeMemberAccess( exp, m.Member );
			}
			return m;
		}

		protected virtual Expression VisitMethodCall( MethodCallExpression m )
		{
			Expression obj = this.Visit( m.Object );
			IEnumerable<Expression> args = this.VisitExpressionList( m.Arguments );
			if ( obj != m.Object || args != m.Arguments )
			{
				return Expression.Call( obj, m.Method, args );
			}
			return m;
		}

		protected virtual ReadOnlyCollection<Expression> VisitExpressionList( ReadOnlyCollection<Expression> original )
		{
			List<Expression> list = null;
			for ( int i = 0, n = original.Count; i < n; i++ )
			{
				Expression p = this.Visit( original[ i ] );
				if ( list != null )
				{
					list.Add( p );
				}
				else if ( p != original[ i ] )
				{
					list = new List<Expression>( n );
					for ( int j = 0; j < i; j++ )
					{
						list.Add( original[ j ] );
					}
					list.Add( p );
				}
			}
			if ( list != null )
			{
				return list.AsReadOnly();
			}
			return original;
		}

		protected virtual MemberAssignment VisitMemberAssignment( MemberAssignment assignment )
		{
			Expression e = this.Visit( assignment.Expression );
			if ( e != assignment.Expression )
			{
				return Expression.Bind( assignment.Member, e );
			}
			return assignment;
		}

		protected virtual MemberMemberBinding VisitMemberMemberBinding( MemberMemberBinding binding )
		{
			IEnumerable<MemberBinding> bindings = this.VisitBindingList( binding.Bindings );
			if ( bindings != binding.Bindings )
			{
				return Expression.MemberBind( binding.Member, bindings );
			}
			return binding;
		}

		protected virtual MemberListBinding VisitMemberListBinding( MemberListBinding binding )
		{
			IEnumerable<ElementInit> initializers = this.VisitElementInitializerList( binding.Initializers );
			if ( initializers != binding.Initializers )
			{
				return Expression.ListBind( binding.Member, initializers );
			}
			return binding;
		}

		protected virtual IEnumerable<MemberBinding> VisitBindingList( ReadOnlyCollection<MemberBinding> original )
		{
			List<MemberBinding> list = null;
			for ( int i = 0, n = original.Count; i < n; i++ )
			{
				MemberBinding b = this.VisitBinding( original[ i ] );
				if ( list != null )
				{
					list.Add( b );
				}
				else if ( b != original[ i ] )
				{
					list = new List<MemberBinding>( n );
					for ( int j = 0; j < i; j++ )
					{
						list.Add( original[ j ] );
					}
					list.Add( b );
				}
			}
			if ( list != null )
				return list;
			return original;
		}

		protected virtual IEnumerable<ElementInit> VisitElementInitializerList( ReadOnlyCollection<ElementInit> original )
		{
			List<ElementInit> list = null;
			for ( int i = 0, n = original.Count; i < n; i++ )
			{
				ElementInit init = this.VisitElementInitializer( original[ i ] );
				if ( list != null )
				{
					list.Add( init );
				}
				else if ( init != original[ i ] )
				{
					list = new List<ElementInit>( n );
					for ( int j = 0; j < i; j++ )
					{
						list.Add( original[ j ] );
					}
					list.Add( init );
				}
			}
			if ( list != null )
				return list;
			return original;
		}

		protected virtual Expression VisitLambda( LambdaExpression lambda )
		{
			Expression body = this.Visit( lambda.Body );
			if ( body != lambda.Body )
			{
				return Expression.Lambda( lambda.Type, body, lambda.Parameters );
			}
			return lambda;
		}

		protected virtual NewExpression VisitNew( NewExpression nex )
		{
			IEnumerable<Expression> args = this.VisitExpressionList( nex.Arguments );
			if ( args != nex.Arguments )
			{
				if ( nex.Members != null )
					return Expression.New( nex.Constructor, args, nex.Members );
				else
					return Expression.New( nex.Constructor, args );
			}
			return nex;
		}

		protected virtual Expression VisitMemberInit( MemberInitExpression init )
		{
			NewExpression n = this.VisitNew( init.NewExpression );
			IEnumerable<MemberBinding> bindings = this.VisitBindingList( init.Bindings );
			if ( n != init.NewExpression || bindings != init.Bindings )
			{
				return Expression.MemberInit( n, bindings );
			}
			return init;
		}

		protected virtual Expression VisitListInit( ListInitExpression init )
		{
			NewExpression n = this.VisitNew( init.NewExpression );
			IEnumerable<ElementInit> initializers = this.VisitElementInitializerList( init.Initializers );
			if ( n != init.NewExpression || initializers != init.Initializers )
			{
				return Expression.ListInit( n, initializers );
			}
			return init;
		}

		protected virtual Expression VisitNewArray( NewArrayExpression na )
		{
			IEnumerable<Expression> exprs = this.VisitExpressionList( na.Expressions );
			if ( exprs != na.Expressions )
			{
				if ( na.NodeType == ExpressionType.NewArrayInit )
				{
					return Expression.NewArrayInit( na.Type.GetElementType(), exprs );
				}
				else
				{
					return Expression.NewArrayBounds( na.Type.GetElementType(), exprs );
				}
			}
			return na;
		}

		protected virtual Expression VisitInvocation( InvocationExpression iv )
		{
			IEnumerable<Expression> args = this.VisitExpressionList( iv.Arguments );
			Expression expr = this.Visit( iv.Expression );
			if ( args != iv.Arguments || expr != iv.Expression )
			{
				return Expression.Invoke( expr, args );
			}
			return iv;
		}
	}
}
